﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace Tnelab.TneCall
{
    public abstract class LWSTneCallServer
    {
        protected readonly Dictionary<WebSocketContext, CancellationTokenSource> CancellationTokenSourceList = new Dictionary<WebSocketContext, CancellationTokenSource>();
        protected WebSocketContext? NativeInvokeContext { get; set; }
        protected HttpListener Listener { get; private set; } = new HttpListener();
        protected Action<string>? Log { get; private set; } = (msg) =>
        {
            Trace.WriteLine("");
            Trace.WriteLine("");
            Trace.WriteLine("");
            Trace.WriteLine("****************************************************************************************************************************************************************");
            Trace.WriteLine(msg,"ERROR");
            Trace.WriteLine("****************************************************************************************************************************************************************");
            Trace.WriteLine("");
            Trace.WriteLine("");
            Trace.WriteLine("");
        };
        public bool IsStarted { get; protected set; } = false;
        public int Port { get; private set; }
        public string Ip { get; private set; }
        public LWSTneCallServer(string ip,int port)
        {
            this.Ip = ip;
            this.Port = port;
        }
        public async Task StartAsync()
        {
            Listener.Prefixes.Add($"http://{this.Ip}:{this.Port}/");
            //Listener.Prefixes.Add($"https://{this.Ip}:{this.Port}/");
            Listener.Start();
            this.IsStarted = true;
            while (this.Listener.IsListening)
            {
                var context = await Listener.GetContextAsync();
                if (!context.Request.IsWebSocketRequest)
                {
                    _ = Task.Factory.StartNew(
                        async (ct) => {
                            try
                            {
                                await this.OnHttpRequest(ct as HttpListenerContext);
                            }
                            catch (Exception ex)
                            {
                                if (this.Log != null)
                                {
                                    this.Log(ex.ToString());
                                }
                            }

                        }, context); ;
                }
                else
                {
                    _ = Task.Factory.StartNew(
                        async (ct) => {
                            try
                            {
                                await this.OnTneCall(ct as HttpListenerContext);
                            }
                            catch (Exception ex)
                            {
                                if (this.Log != null)
                                {
                                    this.Log(ex.ToString());
                                }
                            }
                        }, context);
                }
            }
            this.IsStarted = false;
        }
        public void Stop()
        {
            if (this.IsStarted)
            {
                this.Listener.Stop();
                this.Listener.Close();
                foreach (var cts in this.CancellationTokenSourceList)
                {
                    cts.Value.Cancel();
                }
                this.CancellationTokenSourceList.Clear();
            }
            this.IsStarted = false;
        }
        public void SetLoger(Action<dynamic> loger)
        {
            this.Log = loger;
        }
        public byte[] CallClient(byte[] tneCallInfo)
        {
            if (this.NativeInvokeContext == null)
                throw new InvalidOperationException("NativeInvokeContext不能为空");
            this.NativeInvokeContext.WebSocket.SendAsync(new ArraySegment<byte>(tneCallInfo), WebSocketMessageType.Binary, true, new CancellationTokenSource(1000).Token).GetAwaiter().GetResult();
            using var inStream = new MemoryStream();
            byte[] buffer = new byte[4 * 1024];
            WebSocketReceiveResult wsrResult;
            do
            {
                wsrResult = NativeInvokeContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None).GetAwaiter().GetResult();
                inStream.Write(buffer, 0, wsrResult.Count);
            }
            while (!wsrResult.EndOfMessage);
            return inStream.ToArray();
        }
        private async Task OnHttpRequest(HttpListenerContext? context)
        {
            if (context == null)
                return;
            await Task.Run(() =>
            {
                context.Response.ContentType = "text/html";
                var buffer = new byte[4 * 1024];
                using var stream = this.OnHttpRequest(context.Request);
                if (stream == null)
                {
                    context.Response.Close();
                    return;
                }
                stream.Position = 0;
                int rc = 0;
                do
                {
                    rc = stream.Read(buffer, 0, buffer.Length);
                    context.Response.OutputStream.Write(buffer, 0, rc);
                }
                while (rc != 0);
                context.Response.Close();
            });
        }
        private async Task OnTneCall(HttpListenerContext? context)
        {
            if (context == null)
                return;
            var webSocketContext = await context.AcceptWebSocketAsync(null);
            if (webSocketContext.RequestUri.AbsolutePath.EndsWith("InvokeNative"))
            {
                CancellationTokenSourceList.Add(webSocketContext,new CancellationTokenSource());
                await InvokeNativeHandle(webSocketContext);
                return;
            }
            if (webSocketContext.RequestUri.AbsolutePath.EndsWith("NativeInvoke"))
            {
                this.NativeInvokeContext = webSocketContext;
                return;
            }
        }
        private async Task InvokeNativeHandle(WebSocketContext? context)
        {
            if (context == null)
                return;
            while (!CancellationTokenSourceList[context].IsCancellationRequested)
            {
                try
                {
                    using var inStream = new MemoryStream();
                    byte[] buffer = new byte[4 * 1024];
                    byte[]? tneCallResult = null;
                    WebSocketReceiveResult wsrResult;
                    do
                    {
                        wsrResult = await context.WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                        inStream.Write(buffer, 0, wsrResult.Count);
                    }
                    while (!wsrResult.EndOfMessage);
                    using var stream = this.OnCallServer(inStream.ToArray());
                    tneCallResult = new byte[stream.Length];
                    stream.Position = 0;
                    stream.Read(tneCallResult, 0, tneCallResult.Length);
                    await context.WebSocket.SendAsync(new ArraySegment<byte>(tneCallResult), WebSocketMessageType.Binary, true, new CancellationTokenSource(1000).Token);
                }
                finally
                {
                    this.CancellationTokenSourceList.Remove(context);
                }
            }
        }
        protected abstract Stream OnCallServer(byte[] datas);
        protected abstract Stream? OnHttpRequest(HttpListenerRequest request);
    }
}
